#ifndef CLOUD_H_ZJSGDR4R
#define CLOUD_H_ZJSGDR4R

#include <stdlib.h>
#include <vector>
#include <list>
#include "mover.h"
#include "rain.h"
#include "raindrop.h"
#include "util/math_util.h"
#include "util/vec3f.h"
#include "util/color4f.h"
#include "utility.h"
#include "math.h"

#define MAX_VELOCITY 0.5
#define Y_DISPLAY_THRESHOLD -10
#define NUM_SIDE_SPHERES 4
#define LARGEST_SCALE 0.5
#define GROWTH_RATE 0.05
#define DROPS_PER_FRAME 3
#define MAX_LIVES 5
enum CloudState { CL_RAIN, CL_GROW, CL_SHRINK, CL_EXPIRED };

class Cloud : public Mover {
private:
    GLfloat myScale;
    GLint mySlices; // Also used for stacks
    
    Vec3f myScaleTuple; // scale in x, y, z axes
    
    GLfloat myTargetX;
    
    list<RainDrop*> myRainDrops;
    Vec3f myRainArea;
    Color4f *myRainColor;
    Rain *myRain;
    
    Color4f myColor;
	GLfloat myColorOffset;
    
    GLint myLives;
    
    GLboolean myStates[4];
	struct tm* myTimeInfo;
	Vec3f mySunPos;
	
public:

    Cloud(GLfloat targetX=0, GLboolean rainFlag=false, GLboolean dark=true) {
        myScale = 0.05;
        mySlices = 20;
        
        myVel = Vec3f(0, 0, 0);
        myPos = Vec3f(targetX, 8.0, 2.0);
        myScaleTuple = Vec3f(1.8, 1.0, 1.0);
        
        myTargetX = myPos[0];
        myStates[CL_RAIN] = rainFlag;
        if (dark || rainFlag) {
            myColor.set(0.03, 0.03, 0.03); // Dark cloud color
        } else {
            myColor.set(0.25, 0.25, 0.25);    // Bright cloud color
        }
		myColorOffset = 0.0f;
		        
        myRainColor = new Color4f(0.3, 0.3, 0.6, 0.65);
        myRainArea = Vec3f(1.4, 0.2, 1.0);
        myRain = new Rain(DROPS_PER_FRAME, myPos, myRainArea, myRainColor);
        myStates[CL_GROW] = true;
        myStates[CL_SHRINK] = false;
        myStates[CL_EXPIRED] = false;

        myLives = MAX_LIVES;
		
		myTimeInfo = Utility::getTime();
		GLfloat sun_pos_x = (5.0f/18.0f) * (myTimeInfo->tm_hour*60.0f + myTimeInfo->tm_min) - 200.0f;
		GLfloat sun_pos_y = -(1.0f/400.0f) * pow(sun_pos_x, 2) + 100.0f;
		mySunPos = Vec3f(sun_pos_x, sun_pos_y, 0);
    }
    
    ~Cloud() {
        // RainColor belongs to rain, deleted by rain
        delete myRain;
    }
    
    void draw() {
        glPushMatrix();
            glColor3f(myColor[0], myColor[1], myColor[2]);
            
            // Central, largest ellipsoid of cloud
            glPushMatrix();
                glTranslatef(myPos[0], myPos[1], myPos[2]);
                // Draws an ellipsoid, elongated in the x-dir
                glScalef(myScaleTuple[0], myScaleTuple[1], myScaleTuple[2] * 1.5);
                // args are radius, stacks, slices
                drawSphere(myScale, mySlices, mySlices); 
            glPopMatrix();

            GLfloat dx[NUM_SIDE_SPHERES] = { 0.9, -0.9,  0.2, -0.1};
            GLfloat dy[NUM_SIDE_SPHERES] = {   0,    0,  0.5, -0.3};
            GLfloat dz[NUM_SIDE_SPHERES] = {-0.3, -0.1, -0.2, -0.2};
            // Draw side ellipsoids
            for(size_t i = 0; i < NUM_SIDE_SPHERES; ++i)
            {
                glPushMatrix();
                    glTranslatef(myPos[0] + dx[i], myPos[1] + dy[i], myPos[2] + dz[i]);
            		glScalef(myScaleTuple[0], myScaleTuple[1] * 1.1, myScaleTuple[2]);
            		drawSphere(myScale, mySlices, mySlices);                    
                glPopMatrix();
            }
        
        // Only draw rain if cloud has reached full size
        if (myStates[CL_RAIN] && myScale >= LARGEST_SCALE) {
            myRain->draw();
        }
		glPopMatrix();
    }
    
    void update() {
        gravitateTowardsX(myTargetX); // 5 put in as test
        
        if (myStates[CL_GROW]) {
            growCloud();
        } else if (myStates[CL_SHRINK]) {
            shrinkCloud();
        }
        
        Mover::update();
        
        if (myStates[CL_RAIN]) {
            myRain->update();
        }
		GLfloat sun_pos_x = (5.0f/18.0f) * (myTimeInfo->tm_hour*60.0f + myTimeInfo->tm_min) - 200.0f;
		GLfloat sun_pos_y = -(1.0f/400.0f) * pow(sun_pos_x, 2) + 100.0f;
		mySunPos *= 0.0f;
		mySunPos += Vec3f(sun_pos_x, sun_pos_y, 0.0f);
    }
    
    void setTargetX(GLfloat x) {
        myTargetX = x;
    }
    
    void gravitateTowardsX(GLfloat worldCoordinateX) {
        GLfloat velocityScale = 0.1;
        GLfloat calculatedVelocity = (worldCoordinateX - myPos[0]) * velocityScale;
        
        // Cap at max velocity, both pos and neg
        if (calculatedVelocity < MAX_VELOCITY * (-1)) {
            myVel[0] = MAX_VELOCITY * (-1);
        } else if (calculatedVelocity > MAX_VELOCITY) {
            myVel[0] = MAX_VELOCITY;
        } else {
            myVel[0] = calculatedVelocity;
        }
    }
        
	
	void drawSphere(GLfloat r, int lats, int longs) 
	{
		glCullFace(GL_BACK);		//Cull backfaces
		glEnable(GL_CULL_FACE);		// Enable face culling (front face)
		
		int i, j;
		GLfloat dotProduct;
		Vec3f lightVector(mySunPos[0], mySunPos[1], mySunPos[3]);
		lightVector = lightVector.normalize();
		Vec3f temp(-11, 30, lightVector[2]);
		temp = temp.normalize();
		Vec3f temp1(10, 45, lightVector[2]);
		temp1 = temp1.normalize();
		
		for(i = 0; i <= lats; i++) {
			GLfloat lat0 = M_PI * (-0.5 + (GLfloat) (i - 1) / lats);
			GLfloat z0  = sin(lat0);
			GLfloat zr0 =  cos(lat0);
			
			GLfloat lat1 = M_PI * (-0.5 + (GLfloat) i / lats);
			GLfloat z1 = sin(lat1);
			GLfloat zr1 = cos(lat1);
			
			glBegin(GL_QUAD_STRIP);
			for(j = 0; j <= longs; j++) {
				GLfloat lng = 2 * M_PI * (GLfloat) (j - 1) / longs;
				GLfloat x = cos(lng);
				GLfloat y = sin(lng);
				
				Vec3f normal(x*zr0, z0, y*zr0);
//				glNormal3f(x * zr0, y * zr0, z0);
				dotProduct = (normal.normalize()).dot(lightVector);
				myColorOffset = getGradientVal(dotProduct);
				glColor3f(myColor[0]+myColorOffset, myColor[1]+myColorOffset, myColor[2]+myColorOffset);
				glVertex3f(r * x * zr0, r * z0, r * y * zr0);
				
				normal = Vec3f(x*zr1, z1, y*zr1);
//				glNormal3f(x * zr1, y * zr1, z1);
				dotProduct = (normal.normalize()).dot(lightVector);
				myColorOffset = getGradientVal(dotProduct);
				glColor3f(myColor[0]+myColorOffset, myColor[1]+myColorOffset, myColor[2]+myColorOffset);
				glVertex3f(r * x * zr1, r * z1, r * y * zr1);
			}
			glEnd();
		}
		
		Color4f grayOutline = Color4f(0.1f, 0.1f, 0.1f);
		enableOutlining(grayOutline);
		for(i = 0; i <= lats; i++) 
		{
			GLfloat lat0 = M_PI * (-0.5 + (GLfloat) (i - 1) / lats);
			GLfloat z0  = sin(lat0);
			GLfloat zr0 =  cos(lat0);
			
			GLfloat lat1 = M_PI * (-0.5 + (GLfloat) i / lats);
			GLfloat z1 = sin(lat1);
			GLfloat zr1 = cos(lat1);
			
			glBegin(GL_QUAD_STRIP);
			for(j = 0; j <= longs; j++) 
			{
				GLfloat lng = 2 * M_PI * (GLfloat) (j - 1) / longs;
				GLfloat x = cos(lng);
				GLfloat y = sin(lng);
				glVertex3f(r * x * zr0, r * z0, r * y * zr0);
				glVertex3f(r * x * zr1, r * z1, r * y * zr1);
			}
			glEnd();
		}
		disableOutlining();
	}
	
	GLfloat getGradientVal(GLfloat dotProduct)
	{		
		if(dotProduct < 0.15)
			return 0.1;
		else
			return 0.2;
	}

    static bool comparePosX(Cloud* c1, Cloud* c2) 
    {
        Vec3f pos1 = c1->getPosition();
        Vec3f pos2 = c2->getPosition();
        return pos1[0] < pos2[0];
    }
    
    void startShrink()
    {
        myStates[CL_SHRINK] = true;
        myStates[CL_GROW] = false;
    }

    GLboolean isExpired() 
    {
        return myStates[CL_EXPIRED];
    }

    GLboolean isAlive() {
        if (myLives == 0) {
            myStates[CL_EXPIRED] = true;
            return false;
        }
        return true;
    }

    void takeLife() {
        myLives--;
    }
	
    void renewLife() {
        myLives = MAX_LIVES;
    }

	void enableOutlining(Color4f color)
	{
		glEnable (GL_BLEND);								// Enable Blending
		glBlendFunc (GL_SRC_ALPHA ,GL_ONE_MINUS_SRC_ALPHA);	// Set blend mode
		glPolygonMode (GL_BACK, GL_LINE);					// Draw Backfacing Polygons As Wireframes
		glLineWidth (4.0f);									// Set The Line Width
		glCullFace (GL_FRONT);								// Don't Draw Any Front-Facing Polygons
		glDepthFunc (GL_LEQUAL);							// Change The Depth Mode
		glColor3f(color[0], color[1], color[2]);						// Set The Outline Color
	}
	
	void disableOutlining()
	{
		glDepthFunc (GL_LESS);				// Reset The Depth-Testing Mode
		glCullFace (GL_BACK);				// Reset The Face To Be Culled
		glDisable(GL_CULL_FACE);			// Disable face culling
		glPolygonMode (GL_BACK, GL_FILL);	// Reset Back-Facing Polygon Drawing Mode
		glDisable (GL_BLEND);				// Disable Blending
	}
	
	
private:
    void growCloud() 
    {
        if (myScale < LARGEST_SCALE) {
            myScale += GROWTH_RATE;
        } else {
            myStates[CL_GROW] = false;
        }
    }
    
    void shrinkCloud()
    {
        if (myScale > 0.05) {
            myScale -= GROWTH_RATE;
        } else {
            myStates[CL_SHRINK] = false;
            myStates[CL_EXPIRED] = true;
        }
    }
};

#endif /* end of include guard: CLOUD_H_ZJSGDR4R */
